package cn.rocksolid.proxy.config;

import java.util.Set;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import cn.rocksolid.sport.common.Constant.AccessType;
import cn.rocksolid.sport.common.context.RockSolidContext;
import cn.rocksolid.sport.common.context.RockSolidContextHolder;
import cn.rocksolid.sport.common.context.RockSolidContextImpl;
import cn.rocksolid.sport.common.http.RockSolidContextToken;
import cn.rocksolid.sport.security.entity.TokenEntity;
import cn.rocksolid.sport.security.entity.UserEntity;
import cn.rocksolid.sport.security.service.TokenService;
import cn.rocksolid.sport.security.service.MenuService;
import cn.rocksolid.sport.security.service.UserService;

/**
 * Shiro authorizing realm implementation by RockSolid
 *
 * @author Axl Zhao
 * @email axl.zhao@163.com
 */
@Component
public class ShiroRealm extends AuthorizingRealm {

  private final UserService userService;
  private final MenuService menuService;
  private final TokenService tokenService;

  @Autowired
  public ShiroRealm(final UserService userService, final MenuService menuService, final TokenService tokenService) {
    this.userService = userService;
    this.menuService = menuService;
    this.tokenService = tokenService;
  }

  @Override
  public boolean supports(AuthenticationToken token) {
    return token instanceof RockSolidContextToken;
  }

  /**
   * Authorization
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    RockSolidContext ctx = (RockSolidContext) principals.getPrimaryPrincipal();
    if (AccessType.AGENCY == ctx.getAccessType()) {
      Set<String> permsSet = menuService.queryPermsByUid(ctx.getAccessId());
      SimpleAuthorizationInfo authz = new SimpleAuthorizationInfo();
      authz.setStringPermissions(permsSet);
      return authz;
    }
    throw illegalContext();
  }

  /**
   * Authentication
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
    RockSolidContext ctx = RockSolidContextHolder.getContext();
    if (null == token || AccessType.AGENCY != ctx.getAccessType()) {
      throw illegalTokenType();
    }
    // Validate access token
    TokenEntity sysAccessToken = tokenService.lookup((String) token.getPrincipal());
    if (null == sysAccessToken || sysAccessToken.getExpireTime().getTime() < System.currentTimeMillis()) {
      throw illegalToken();
    }
    // get user information
    UserEntity sysUser = userService.get(sysAccessToken.getAccessId());
    if (null == sysUser) {
      throw illegalToken();
    }
    RockSolidContextImpl grantCtx = new RockSolidContextImpl(ctx);
    grantCtx.setAccessId(sysUser.getId());
    RockSolidContextHolder.setContext(grantCtx);
    return new SimpleAuthenticationInfo(grantCtx, token.getPrincipal(), getName());
  }

  private IncorrectCredentialsException illegalTokenType() {
    return new IncorrectCredentialsException("Illegal type of access token, please contact system manager.");
  }

  private IncorrectCredentialsException illegalToken() {
    return new IncorrectCredentialsException("Illegal access token, please re-log into the system.");
  }

  private IncorrectCredentialsException illegalContext() {
    return new IncorrectCredentialsException("Illegal type of access, please contact system manager.");
  }
}
